import { 
  createContext as _createContext,
  useContext as _useContext,
  useRef,
  useLayoutEffect,
  useSyncExternalStore,
  Context,
  ProviderProps
} from 'react';

import shallowequal from 'shallowequal'

type Listener = () => void

interface ICValue<V> {
  value: V,
  subscribe: (callback: Listener) => any,
  dispatch: () => any
}

export function createContext<V>(defaultValue: V): Context<V> {
  const context = _createContext<ICValue<V>>({
    value: defaultValue,
    subscribe: (cb: Listener) => {cb()},
    dispatch: () => {}
  })

  const _Provider = context.Provider

  const Provider = (props: ProviderProps<V>) => {
    const {value, children} = props

    const store = useRef<ICValue<V>>()

    if (!store.current) {
      const listeners = new Set<Listener>()

      store.current = {
        value,
        subscribe: (callback) => {
          listeners.add(callback)
          return () => listeners.delete(callback)
        },
        dispatch: () => listeners.forEach((cb) => cb())
      }
    }

    useLayoutEffect(() => {
      if (store.current) {
        store.current.value = value;
        store.current.dispatch()
      }
    }, [value])

    return (
      <_Provider value={store.current}>
        {children}
      </_Provider>
    )
  }

  // @ts-ignore
  context.Provider = Provider
  // @ts-ignore
  delete context.Consumer
  // @ts-ignore
  return context
}

export function useContextSelector<V, S>(
  context: Context<V>,
  selector: (value: V) => S
) {
  // @ts-ignore
  const store = _useContext<ICValue<V>>(context);

  const lastSnapshot = useRef(selector(store.value));

  const getSnapshot = () => {
    const nextSnapshot = selector(store.value);

    if (shallowequal(nextSnapshot, lastSnapshot.current)) {
      return lastSnapshot.current;
    }

    lastSnapshot.current = nextSnapshot;
    return nextSnapshot;
  };

  return useSyncExternalStore(store.subscribe, getSnapshot);
}
